package cc.owoo.godpen.analysis.html;

import cc.owoo.godpen.analysis.Retract;
import cc.owoo.godpen.analysis.TextBuilder;
import cc.owoo.godpen.analysis.css.Property;
import cc.owoo.godpen.analysis.css.Selector;
import cc.owoo.godpen.analysis.html.extract.Extract;
import cc.owoo.godpen.analysis.json.Json;

import java.util.*;
import java.util.function.Consumer;

/**
 * 元素
 * Created by nimensei
 * 2022-05-21 上午 01:58
 **/
public class Element implements Node {
    static final HashSet<String> IGNORE = new HashSet<>(List.of("html", "head", "body"));// 选择忽略的标签
    static final HashSet<String> QUOTE = new HashSet<>(List.of("meta", "title", "script", "link", "template"));// 引用类型标签
    static final HashSet<String> INDIVIDUAL = new HashSet<>(List.of("meta", "link", "input", "img", "br", "br", "hr", "frame", "area", "base", "col", "embed", "keygen", "source", "param"));// 单标签
    static final HashSet<String> ONLY = new HashSet<>(List.of("button"));// 不能有相同名称的子元素
    static final HashSet<String> ORIGINAL = new HashSet<>(List.of("pre", "script", "style", "template"));// 原文解析
    static final HashSet<String> TEXT = new HashSet<>(List.of("title", "script", "style", "template"));// 文本内容
    static final HashSet<String> INLINE = new HashSet<>(List.of("a", "b", "u", "span", "img", "input", "strong", "select", "sub", "sup", "label", "em", "button", "textarea", "tt", "var", "samp", "br", "cite", "code", "font", "strike"));// 行内元素
    private final HashMap<String, Selector> selectorCache = new HashMap<>();// 选择器缓存
    private final HashMap<String, Extract> extractCache = new HashMap<>();// 提取器缓存
    private final ElementMap tagNameMap = new ElementMap(ElementMap.ElementType.ELEMENT_CHILD);// 标签子元素映射表
    private final ElementMap nameMap = new ElementMap(ElementMap.ElementType.ELEMENT_CHILD);// 名称元素映射表
    private final ElementMap classNameMap = new ElementMap(ElementMap.ElementType.ELEMENT_CHILD);// class子元素映射表
    private Element parent;// 父元素
    private Element prev;// 上一个元素
    private Element next;// 下一个元素
    private final ElementChildList childElements = new ElementChildList();// 子元素
    private final NodeList childNodes = new NodeList();// 包括文字在内的子元素
    final LinkedHashMap<String, String> attribute = new LinkedHashMap<>();// 属性
    private final String tagName;// 标签名称
    final boolean isSingle;// 是否为单标签
    private final boolean isQuote;// 是否为引用类型的标签
    private final boolean isOriginal;// 是否为保持原文的标签
    private final boolean isInline;// 是否为行内元素
    private String id;// ID名称
    private String name;// 名称
    private String className;// class名称
    private Property style;// 样式属性

    Element(String tagName) {
        this.tagName = tagName;
        isSingle = INDIVIDUAL.contains(tagName);
        isQuote = QUOTE.contains(tagName);
        isInline = INLINE.contains(tagName);
        isOriginal = ORIGINAL.contains(tagName);
    }

    /**
     * 获取上一个元素
     *
     * @return 元素对象
     */
    public Element getPrev() {
        return prev;
    }

    /**
     * 获取下一个元素
     *
     * @return 元素对象
     */
    public Element getNext() {
        return next;
    }

    /**
     * 执行选择器
     *
     * @param selector 选择器代码
     * @return 选择器执行结果
     */
    public LinkedList<Element> select(String selector) {
        if (selector == null)
            return null;
        String string = selector.trim();
        return select(selectorCache.computeIfAbsent(string, v -> Selector.parse(string)));
    }

    /**
     * 执行选择器
     *
     * @param selector 选择器
     * @return 选择器执行结果
     */
    public LinkedList<Element> select(Selector selector) {
        return selector.select(this);
    }

    /**
     * 执行提取器
     *
     * @param extract 提取器脚本
     * @return 提取结果
     */
    public Json extract(String extract) {
        if (extract == null)
            return null;
        return extract(extractCache.computeIfAbsent(extract.trim(), v -> Extract.parse(extract)));
    }

    /**
     * 执行提取器
     *
     * @param extract 提取器
     * @return 提取结果
     */
    private Json extract(Extract extract) {
        return null;
    }

    /**
     * 获取所有子元素的文本
     *
     * @return 文本
     */
    public String getInnerText() {
        TextBuilder string = new TextBuilder();
        innerText(string);
        return string.toString();
    }

    /**
     * 拼接所有文本
     *
     * @param string 字符串
     */
    private void innerText(TextBuilder string) {
        if (!isVisible())
            return;
        boolean isBlock = isBlock();
        if (isBlock || tagName.equals("br"))
            string.append('\n');
        if (isQuote || isSingle)
            return;
        if (childNodes.size() == 0)
            return;
        for (Node node : childNodes) {
            if (node instanceof Text text)
                string.append(text.text);
            else if (node instanceof Element element)
                element.innerText(string);
        }
        if (isBlock)
            string.append('\n');
    }

    /**
     * 获取所有子元素的html代码
     *
     * @return 所有子元素的序列化HTML代码
     */
    public String getInnerHTML() {
        StringBuilder string = new StringBuilder();
        stringify(string, false, false);
        return string.toString();
    }

    /**
     * 获取标签名称
     *
     * @return 标签名称
     */
    public String getTagName() {
        return tagName;
    }

    /**
     * 设置元素ID
     *
     * @param id ID名称
     * @return 被替换掉的值
     */
    public String setId(String id) {
        if (Objects.equals(id, this.id))
            return id;
        if (parent != null)
            parent.onUpdateID(this.id, id, this);
        String old = this.id;
        this.id = id;
        return old;
    }

    /**
     * 获取元素ID
     *
     * @return ID值
     */
    public String getId() {
        return id;
    }

    /**
     * 子元素的ID发生改变
     *
     * @param old     旧的值
     * @param now     新的值
     * @param element 元素对象
     */
    void onUpdateID(String old, String now, Element element) {
        if (parent != null)
            parent.onUpdateID(old, now, element);
    }

    /**
     * 设置元素名称
     *
     * @param name 元素名称
     * @return 被替换掉的值
     */
    public String setName(String name) {
        if (Objects.equals(name, this.name))
            return name;
        if (parent != null)
            parent.onUpdateName(this.name, name, this);
        String old = this.name;
        this.name = name;
        return old;
    }

    /**
     * 获取名称值
     *
     * @return 名称
     */
    public String getName() {
        return name;
    }

    /**
     * 子元素的名称发生改变
     *
     * @param old     旧的值
     * @param now     新的值
     * @param element 元素对象
     */
    void onUpdateName(String old, String now, Element element) {
        nameMap.remove(old, element);
        nameMap.add(now, element);
        if (parent != null)
            parent.onUpdateName(old, now, element);
    }


    /**
     * 设置元素类名称
     *
     * @param className 类名称
     * @return 被替换掉的值
     */
    public String setClassName(String className) {
        if (Objects.equals(className, this.className))
            return className;
        if (parent != null)
            parent.onUpdateClassName(this.className, className, this);
        String old = this.className;
        this.className = className;
        return old;
    }

    /**
     * 获取class名称
     *
     * @return class名称
     */
    public String getClassName() {
        return className;
    }

    /**
     * 子元素的类名称发生改变
     *
     * @param old     旧的值
     * @param now     新的值
     * @param element 元素对象
     */
    void onUpdateClassName(String old, String now, Element element) {
        classNameMap.remove(old, element);
        classNameMap.add(now, element);
        if (parent != null)
            parent.onUpdateClassName(old, now, element);
    }

    /**
     * 设置内联样式
     *
     * @param string 样式属性代码
     * @return 被替换掉的值
     */
    public Property setStyle(String string) {
        Property property = Property.parse(string);
        return setStyle(property == null && string != null ? new Property() : property);
    }

    /**
     * 设置内联样式属性对象
     *
     * @param style 样式属性对象
     * @return 被替换掉的值
     */
    public Property setStyle(Property style) {
        if (this.style == style)
            return null;
        Property old = this.style;
        this.style = style;
        return old;
    }

    /**
     * 获取内联样式属性代码
     *
     * @return 样式属性代码
     */
    public String getStyleString() {
        return style == null ? null : style.stringifyParenthesisFree();
    }

    /**
     * 获取内联样式属性对象
     *
     * @return 内联样式属性对象
     */
    public Property getStyle() {
        return style;
    }

    /**
     * 获取内联样式属性对象，如果内联样式对象为空则创建一个
     *
     * @return 内联样式属性对象
     */
    public Property getStyleIfNull() {
        return style == null ? style = new Property() : style;
    }

    /**
     * 设置属性
     *
     * @param key   属性键
     * @param value 属性值
     */
    public void setAttribute(String key, String value) {
        if (key == null || (key = key.trim().toLowerCase()).length() == 0)
            return;
        putAttribute(key.trim().toLowerCase(), value);
    }

    /**
     * 设置属性，并检查属性是否已存在，如果存在则不进行设置
     *
     * @param key   属性键
     * @param value 属性值
     */
    public boolean setAttributeIfAbsent(String key, String value) {
        if (key == null || (key = key.trim().toLowerCase()).length() == 0)
            return false;
        if (attribute.containsKey(key))
            return false;
        putAttribute(key, value);
        return true;
    }

    /**
     * 添加属性
     *
     * @param key   属性键
     * @param value 属性值
     */
    private void putAttribute(String key, String value) {
        switch (key) {
            case "id" -> setId(value);
            case "name" -> setName(value);
            case "class" -> setClassName(value);
            case "style" -> setStyle(value);
            default -> attribute.put(key, value);
        }
    }

    /**
     * 获取属性值
     *
     * @param key 属性键
     * @return 属性值
     */
    public String getAttribute(String key) {
        if (key == null || (key = key.trim().toLowerCase()).length() == 0)
            return null;
        return switch (key) {
            case "id" -> getId();
            case "name" -> getName();
            case "class" -> getClassName();
            case "style" -> getStyleString();
            default -> attribute.get(key);
        };
    }

    /**
     * 判断是否存在属性键
     *
     * @param key 属性键
     * @return 如果属性存在则返回true
     */
    public boolean hasAttribute(String key) {
        if (key == null || (key = key.trim().toLowerCase()).length() == 0)
            return false;
        return switch (key) {
            case "id" -> id != null;
            case "name" -> name != null;
            case "class" -> className != null;
            case "style" -> style != null;
            default -> attribute.containsKey(key);
        };
    }

    /**
     * 删除属性
     *
     * @param key 属性键
     * @return 被删除的属性的值
     */
    public String removeAttribute(String key) {
        if (key == null || (key = key.trim().toLowerCase()).length() == 0)
            return null;
        return switch (key) {
            case "id" -> setId(null);
            case "name" -> setName(null);
            case "class" -> setClassName(null);
            case "style" -> {
                Property property = setStyle((Property) null);
                yield property == null ? null : property.stringifyParenthesisFree();
            }
            default -> attribute.remove(key);
        };
    }

    /**
     * 获取父级元素
     *
     * @return 元素对象
     */
    public Element getParent() {
        return parent;
    }

    /**
     * 添加子节点
     *
     * @param node 需要添加的节点
     */
    public void appendChild(Node node) {
        if (isSingle)
            throw new IllegalArgumentException("<" + tagName + ">为单标签，不能添加子节点");
        if (isQuote)
            throw new IllegalArgumentException("<" + tagName + ">为头部标签，不能添加子节点");
        appendChildNode(node);
    }

    /**
     * 添加子节点
     *
     * @param node 需要添加的节点
     */
    void appendChildNode(Node node) {
        if (node instanceof Element element) {
            if (element.parent != null)
                throw new IllegalArgumentException("该元素的父元素不为空");
            element.parent = this;
            if (childElements.size() > 0) {
                Element prev = childElements.get(childElements.size() - 1);
                prev.next = element;
                element.prev = prev;
            }
            childElements.add(element);
            appendRecord(element);
            if (element.childElements.length() > 0)
                for (Element item : element.getAllChildElement())
                    appendRecord(item);
        } else if (node instanceof Text text) {
            if (childNodes.size() > 0 && childNodes.get(childNodes.size() - 1) instanceof Text last) {
                last.text += text.text;
                return;
            }
        }
        childNodes.add(node);
    }

    /**
     * 删除子节点
     *
     * @param node 需要删除的节点
     * @return 是否删除成功
     */
    public boolean removeChild(Node node) {
        if (isSingle || isQuote)
            return false;
        if (node instanceof Element element) {
            if (element.parent != this)
                return false;
            childElements.remove(element);
            if (element.prev != null)
                element.prev.next = element.next;
            if (element.next != null)
                element.next.prev = element.prev;
            element.prev = element.next = null;
            removeRecord(element);
            if (element.childElements.length() > 0)
                for (Element item : element.getAllChildElement())
                    removeRecord(item);
            element.parent = null;
        }
        return childNodes.remove(node);
    }

    /**
     * 获取子元素
     *
     * @return 元素数组
     */
    public ElementList getChildElements() {
        return childElements;
    }

    /**
     * 获取所有子元素节点
     *
     * @return 节点数组
     */
    public NodeList getChildNodes() {
        return childNodes;
    }

    /**
     * 添加记录标签元素的id","class","name
     *
     * @param element 需要被记录的元素
     */
    void appendRecord(Element element) {
        tagNameMap.add(element.tagName, element);
        nameMap.add(element.name, element);
        classNameMap.add(element.className, element);
        if (parent != null)
            parent.appendRecord(element);
    }

    /**
     * 删除记录
     *
     * @param element 需要删除的元素
     */
    void removeRecord(Element element) {
        tagNameMap.remove(element.tagName, element);
        nameMap.remove(element.name, element);
        classNameMap.remove(element.className, element);
        if (parent != null)
            parent.removeRecord(element);
    }

    /**
     * 根据名称获取元素
     *
     * @param tagName 名称
     * @return 元素数组
     */
    public ElementList getElementsByName(String tagName) {
        return nameMap.get(tagName);
    }

    /**
     * 根据标签名称获取元素
     *
     * @param tagName 标签名称
     * @return 元素数组
     */
    public ElementList getElementsByTagName(String tagName) {
        return tagNameMap.get(tagName);
    }

    /**
     * 根据class名称获取元素
     *
     * @param className class名称
     * @return 元素数组
     */
    public ElementList getElementsByClassName(String className) {
        return classNameMap.get(className);
    }

    /**
     * 获取该元素所在的dom
     *
     * @return 该元素所在的dom
     */
    public Document getDocument() {
        return parent == null ? null : parent.getDocument();
    }

    /**
     * 是否为单标签
     *
     * @return 如果标签名称是br","input等单标签，就返回true
     */
    public boolean isSingle() {
        return isSingle;
    }

    /**
     * 是否为头部标签，如link","title","script等标签，这些标签一般作用于浏览器，获取父级元素的innerText时，会忽略这些标签
     *
     * @return 如果标签名称为link","title","script等引用类型，则返回true
     */
    public boolean isQuote() {
        return isQuote;
    }

    /**
     * 是否为行内元素或行内块元素
     *
     * @return 是否为行内元素
     */
    public boolean isInline() {
        if (style == null)
            return isInline;
        String display = style.get("display");
        return isInline && (display != null && !display.equals("block"));
    }

    /**
     * 是否为块级元素
     *
     * @return 是否为块级元素
     */
    public boolean isBlock() {
        if (style == null)
            return !isInline;
        String display = style.get("display");
        return !isInline && (display != null && !display.startsWith("inline"));
    }

    /**
     * 元素本身是否可见（不包括父元素）
     *
     * @return 如果元素的样式表示为可见，那么返回true，否则返回false
     */
    public boolean isVisible() {
        if (style == null)
            return true;
        String display = style.get("display");
        String visibility = style.get("visibility");
        if (display != null && display.equals("none"))
            return false;
        return visibility == null || visibility.equals("visible");
    }

    /**
     * 获取所有属性
     *
     * @return 属性列表
     */
    private LinkedList<Attribute> getAllAttribute() {
        LinkedList<Attribute> attributes = new LinkedList<>();
        if (id != null)
            attributes.add(new Attribute("id", id));
        if (className != null)
            attributes.add(new Attribute("class", className));
        if (name != null)
            attributes.add(new Attribute("name", name));
        attribute.forEach((key, value) -> attributes.add(new Attribute(key, value)));
        if (style != null)
            attributes.add(new Attribute("style", style.stringifyParenthesisFree()));
        return attributes;
    }

    /**
     * 遍历所有有效属性
     *
     * @param action 属性迭代器
     */
    public void forEachAttribute(Consumer<Attribute> action) {
        if (action == null)
            return;
        getAllAttribute().forEach(action);
    }

    /**
     * 获取所有子元素（不包含当前元素）
     *
     * @return 集合对象，遍历的结果会添加到这个集合中
     */
    public LinkedList<Element> getAllChildElement() {
        LinkedList<Element> list = new LinkedList<>();
        appendChildElement(list);
        return list;
    }

    /**
     * 获取所有子元素（不包含当前元素）
     *
     * @param collection 集合对象，遍历的结果会添加到这个集合中
     */
    public void getAllChildElement(Collection<Element> collection) {
        if (collection == null)
            throw new NullPointerException("集合对象不能为空");
        appendChildElement(collection);
    }

    /**
     * 添加所有子元素添加到集合中（不包含当前元素）
     *
     * @param collection 集合对象，遍历的结果会添加到这个集合中
     */
    private void appendChildElement(Collection<Element> collection) {
        for (Element element : childElements) {
            collection.add(element);
            element.appendChildElement(collection);
        }
    }

    /**
     * 获取所有子节点（不包含当前节点）
     *
     * @return 集合对象，遍历的结果会添加到这个集合中
     */
    public LinkedList<Node> getAllChildNode() {
        LinkedList<Node> list = new LinkedList<>();
        appendChildNodes(list);
        return list;
    }

    /**
     * 获取所有子节点（不包含当前节点）
     *
     * @param collection 集合对象，遍历的结果会添加到这个集合中
     */
    public void getAllChildNode(Collection<Node> collection) {
        if (collection == null)
            throw new NullPointerException("集合对象不能为空");
        appendChildNodes(collection);
    }

    /**
     * 添加所有子节点添加到集合中（不包含当前节点）
     *
     * @param collection 集合对象，遍历的结果会添加到这个集合中
     */
    private void appendChildNodes(Collection<Node> collection) {
        for (Node node : childNodes) {
            collection.add(node);
            if (node instanceof Element element)
                element.appendChildNodes(collection);
        }
    }

    /**
     * 序列化HTML代码
     *
     * @param string  序列化的字符串
     * @param root    是否序列化根元素
     * @param retract 缩进控制
     */
    void stringify(StringBuilder string, boolean root, Retract retract, boolean isOriginal) {
        if (root && stringifyElement(string))
            return;
        retract.add(1);
        boolean ln = retract.isEnable();
        int count = 0;
        boolean isChildOriginal = this.isOriginal || isOriginal;
        for (Node node : childNodes) {
            if (node instanceof Text text) {
                if (isChildOriginal) {
                    stringifyValue(string, text.text);
                    continue;
                }
                if (text.text.isBlank()) {
                    if (childNodes.size() == 1 || !retract.isEnable()) {
                        if (text.text.length() > 0)
                            string.append(' ');
                        ln = false;
                    }
                    continue;
                }
                if (!retract.isEnable()) {
                    stringifyValue(string, text.text);
                    continue;
                }
                if (ln) {
                    ln = count > 0 || (childNodes.size() > 1 && Character.isWhitespace(text.text.charAt(text.text.length() - 1)));
                    if (ln && Character.isWhitespace(text.text.charAt(0))) {
                        ++count;
                        string.append('\n').append(retract.get());
                        stringifyValue(string, text.text.strip());
                    } else {
                        if (ln)
                            stringifyValue(string, text.text.stripTrailing());
                        else
                            stringifyValue(string, text.text);
                    }
                } else
                    stringifyValue(string, text.text);
            } else {
                if (!isChildOriginal) {
                    if (ln) {
                        ++count;
                        string.append('\n').append(retract.get());
                    } else
                        ln = retract.isEnable();
                }
                if (node instanceof Annotation annotation)
                    string.append("<!--").append(annotation.text).append("-->");
                else if (node instanceof Element element)
                    element.stringify(string, true, retract, isChildOriginal);
                else
                    string.append(node.toString());
            }
        }
        if (!root)
            return;
        retract.add(-1);
        if (!isOriginal && ln && count > 0)
            string.append('\n').append(retract.get());
        stringifyElementEnd(string);
    }

    /**
     * 序列化HTML元素
     *
     * @param string 序列化的字符串将会拼接到string中
     * @param format 是否格式化代码
     */
    public void stringify(StringBuilder string, boolean root, boolean format) {
        stringify(string, root, new Retract() {{
            setEnable(format);
        }}, false);
    }

    /**
     * 序列化元素，如果元素没有子元素，或只有一个文本子元素，则进行序列化并返回true，否则返回false
     *
     * @param string 序列化结果
     * @return 如果元素没有子元素，或只有一个文本子元素，则进行序列化并返回true，否则返回false
     */
    private boolean stringifyElement(StringBuilder string) {
        string.append('<').append(tagName);
        boolean isSingleName = false;
        for (Attribute attribute : getAllAttribute()) {
            String name = attribute.name().trim();
            if (name.length() == 0 || name.equals("/"))
                continue;
            String value = attribute.value();
            string.append(' ');
            if (value == null || value.length() == 0) {
                if (name.length() == 1) {
                    isSingleName = true;
                    string.append(name);
                } else {
                    if (isSingleName && name.charAt(0) == '=')
                        string.append('/');
                    string.append(name);
                    if (name.charAt(name.length() - 1) == '=') {
                        string.append('/');
                        isSingleName = false;
                    } else
                        isSingleName = true;
                }
            } else {
                string.append(name).append('=').append('"');
                stringifyValue(string, value);
                string.append('"');
                isSingleName = false;
            }
        }
        string.append('>');
        if (isSingle)
            return true;
        if (childNodes.length() == 0) {
            stringifyElementEnd(string);
            return true;
        }
        return false;
    }

    /**
     * 序列化值
     *
     * @param stringBuilder 序列化结果
     * @param string        需要序列化的值字符串
     */
    private void stringifyValue(StringBuilder stringBuilder, String string) {
        for (char c : string.toCharArray()) {
            switch (c) {
                case '"' -> stringBuilder.append("&quot;");
                case '&' -> stringBuilder.append("&amp;");
                case '<' -> stringBuilder.append("&lt;");
                case '>' -> stringBuilder.append("&gt;");
                default -> stringBuilder.append(c);
            }
        }
    }

    /**
     * 序列化元素结尾标签
     *
     * @param string 序列化的字符串将会拼接到string中
     */
    public void stringifyElementEnd(StringBuilder string) {
        string.append("</").append(tagName).append('>');
    }

    /**
     * 序列化
     *
     * @param format 是否格式化HTML代码
     */
    public String stringify(boolean format) {
        StringBuilder string = new StringBuilder();
        stringify(string, true, format);
        return string.toString();
    }


    /**
     * 序列化
     *
     * @return 序列化HTML代码
     */
    public String stringify() {
        return stringify(false);
    }

    /**
     * 序列化并格式化HTML代码
     *
     * @return 序列化HTML代码
     */
    public String stringifyFormat() {
        return stringify(true);
    }

    @Override
    public String toString() {
        StringBuilder string = new StringBuilder();
        stringifyElement(string);
        return string.toString();
    }

    /**
     * 用于遍历的元素节点
     */
    private static class ForEachElement {
        public Element element;// 元素
        public ElementChildList children;// 子节点列表
        public int index;// 当前序列化位置

        public ForEachElement(Element element) {
            this.element = element;
            this.children = element.childElements;
        }
    }

    /**
     * 用于序列化的元素节点
     */
    private static class StringifyElement {
        public Element element;// 元素
        public NodeList children;// 子节点列表
        public int index;// 当前序列化位置
        public boolean isOriginal;// 是否开启原文序列化

        public StringifyElement(Element element) {
            this.element = element;
            this.children = element.childNodes;
        }
    }
}
